using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;

namespace OpenTK.Windowing.Desktop
{
	public class GameWindow : NativeWindow
	{
		private const double MaxFrequency = 500.0;

		private readonly Stopwatch _watchRender = new Stopwatch();

		private readonly Stopwatch _watchUpdate = new Stopwatch();

		private double _updateEpsilon;

		private double _renderFrequency;

		private double _updateFrequency;

		private Thread _renderThread;

		protected bool IsRunningSlowly { get; private set; }

		[Obsolete("There is not one size fits all multithreading solution, especially for OpenGL. This option will be removed in future versions, and you will have to implement what you need instead.")]
		public bool IsMultiThreaded { get; }

		public double RenderFrequency
		{
			get
			{
				return _renderFrequency;
			}
			set
			{
				if (value <= 1.0)
				{
					_renderFrequency = 0.0;
				}
				else if (value <= 500.0)
				{
					_renderFrequency = value;
				}
				else
				{
					_renderFrequency = 500.0;
				}
			}
		}

		public double RenderTime { get; protected set; }

		public double UpdateTime { get; protected set; }

		public double UpdateFrequency
		{
			get
			{
				return _updateFrequency;
			}
			set
			{
				if (value < 1.0)
				{
					_updateFrequency = 0.0;
				}
				else if (value <= 500.0)
				{
					_updateFrequency = value;
				}
				else
				{
					_updateFrequency = 500.0;
				}
			}
		}

		public event Action Load;

		public event Action Unload;

		public event Action<FrameEventArgs> UpdateFrame;

		public event Action RenderThreadStarted;

		public event Action<FrameEventArgs> RenderFrame;

		public GameWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
			: base(nativeWindowSettings)
		{
			IsMultiThreaded = gameWindowSettings.IsMultiThreaded;
			RenderFrequency = gameWindowSettings.RenderFrequency;
			UpdateFrequency = gameWindowSettings.UpdateFrequency;
		}

		[DllImport("winmm")]
		private static extern uint timeBeginPeriod(uint uPeriod);

		[DllImport("winmm")]
		private static extern uint timeEndPeriod(uint uPeriod);

		public unsafe virtual void Run()
		{
			base.Context?.MakeCurrent();
			if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
			{
				timeBeginPeriod(1u);
			}
			OnLoad();
			OnResize(new ResizeEventArgs(base.Size));
			if (IsMultiThreaded)
			{
				base.Context?.MakeNoneCurrent();
				_renderThread = new Thread(StartRenderThread);
				_renderThread.Start();
			}
			_watchRender.Start();
			_watchUpdate.Start();
			while (!GLFW.WindowShouldClose(base.WindowPtr))
			{
				double num = DispatchUpdateFrame();
				if (!IsMultiThreaded)
				{
					double val = DispatchRenderFrame();
					num = Math.Min(num, val);
				}
				if (num > 0.0)
				{
					Thread.Sleep((int)Math.Floor(num * 1000.0));
				}
			}
			OnUnload();
			if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
			{
				timeEndPeriod(1u);
			}
		}

		private unsafe void StartRenderThread()
		{
			base.Context?.MakeCurrent();
			OnRenderThreadStarted();
			_watchRender.Start();
			while (!GLFW.WindowShouldClose(base.WindowPtr))
			{
				DispatchRenderFrame();
			}
		}

		private double DispatchUpdateFrame()
		{
			int num = 4;
			double totalSeconds = _watchUpdate.Elapsed.TotalSeconds;
			double num2 = ((UpdateFrequency == 0.0) ? 0.0 : (1.0 / UpdateFrequency));
			while (totalSeconds > 0.0 && totalSeconds + _updateEpsilon >= num2)
			{
				ProcessInputEvents();
				NativeWindow.ProcessWindowEvents(base.IsEventDriven);
				_watchUpdate.Restart();
				UpdateTime = totalSeconds;
				OnUpdateFrame(new FrameEventArgs(totalSeconds));
				_updateEpsilon += totalSeconds - num2;
				if (UpdateFrequency <= double.Epsilon)
				{
					break;
				}
				IsRunningSlowly = _updateEpsilon >= num2;
				if (IsRunningSlowly && --num == 0)
				{
					_updateEpsilon = 0.0;
					break;
				}
				totalSeconds = _watchUpdate.Elapsed.TotalSeconds;
			}
			if (UpdateFrequency != 0.0)
			{
				return num2 - totalSeconds;
			}
			return 0.0;
		}

		private double DispatchRenderFrame()
		{
			double totalSeconds = _watchRender.Elapsed.TotalSeconds;
			double num = ((RenderFrequency == 0.0) ? 0.0 : (1.0 / RenderFrequency));
			if (totalSeconds > 0.0 && totalSeconds >= num)
			{
				_watchRender.Restart();
				RenderTime = totalSeconds;
				OnRenderFrame(new FrameEventArgs(totalSeconds));
				if (base.VSync == VSyncMode.Adaptive)
				{
					GLFW.SwapInterval((!IsRunningSlowly) ? 1 : 0);
				}
			}
			if (RenderFrequency != 0.0)
			{
				return num - totalSeconds;
			}
			return 0.0;
		}

		public virtual void SwapBuffers()
		{
			if (base.Context == null)
			{
				throw new InvalidOperationException("Cannot use SwapBuffers when running with ContextAPI.NoAPI.");
			}
			base.Context.SwapBuffers();
		}

		public override void Close()
		{
			base.Close();
		}

		protected virtual void OnRenderThreadStarted()
		{
			this.RenderThreadStarted?.Invoke();
		}

		protected virtual void OnLoad()
		{
			this.Load?.Invoke();
		}

		protected virtual void OnUnload()
		{
			this.Unload?.Invoke();
		}

		protected virtual void OnUpdateFrame(FrameEventArgs args)
		{
			this.UpdateFrame?.Invoke(args);
		}

		protected virtual void OnRenderFrame(FrameEventArgs args)
		{
			this.RenderFrame?.Invoke(args);
		}
	}
}
